use serde::Serialize;
use tokio::fs;

use crate::{
    config::FIRM_DIR, error::{ec, new_err_msg, AppErr, IoErr, SqlxErr}, store::get_pool, utils::current_timestamp
};

async fn write_file(id: u64, body: &[u8]) -> Result<(), IoErr> {
    fs::write(format!("{}/{}", FIRM_DIR, id), body).await?;
    Ok(())
}

async fn delete_file(id: u64) -> Result<(), IoErr> {
    fs::remove_file(format!("{}/{}", FIRM_DIR, id)).await?;
    Ok(())
}

async fn read_file(id: u64) -> Result<Box<[u8]>, IoErr> {
    let data = fs::read(format!("{}/{}", FIRM_DIR, id)).await?;
    Ok(data.into_boxed_slice())
}

pub async fn create(
    firm_type: u8,
    version: &str,
    description: &str,
    name: &str,
    body: &[u8],
) -> Result<u64, AppErr> {
    let mut tx = get_pool().begin().await?;

    let now = current_timestamp();
    let ret = sqlx::query!(
        r#"
        INSERT INTO tb_firm (firm_type, version, description, size, name, create_timestamp) 
        VALUES (?, ?, ?, ?, ?, ?)    
    "#,
        firm_type,
        version,
        description,
        body.len() as u32,
        name,
        now
    )
    .execute(&mut *tx).await;
    match ret {
        Ok(r) => {
            let id = r.last_insert_id();
            write_file(id, body).await?;
            tx.commit().await?;
            Ok(id)
        }
        Err(SqlxErr::Database(err)) if err.is_unique_violation() => {
            Err(new_err_msg(ec::FIRM_IS_EXISTS, ec::FIRM_IS_EXISTS_MSG))
        }
        Err(e) => Err(e.into())
    }
}

#[derive(Debug, Serialize)]
pub struct TbFirm {
    pub id: u64,
    version: String,
    description: String,
    size: u32,
    name: String,
    create_timestamp: u64,
}

pub async fn query_all(firm_type: u8) -> Result<Box<[TbFirm]>, SqlxErr> {
    let res = sqlx::query_as!(
        TbFirm,
        r#"SELECT id, version, description, size, name, create_timestamp 
        FROM tb_firm 
        WHERE firm_type = ? 
        ORDER BY create_timestamp DESC"#,
        firm_type
    )
    .fetch_all(get_pool())
    .await?;

    Ok(res.into_boxed_slice())
}

pub async fn delete(id: u64) -> Result<(), AppErr> {
    let mut tx = get_pool().begin().await?;
    sqlx::query!("DELETE FROM tb_firm WHERE id = ?", id)
        .execute(&mut *tx)
        .await?;
    delete_file(id).await?;
    tx.commit().await?;
    Ok(())
}

pub struct FirmFile {
    pub name: String,
    pub body: Box<[u8]>,
}

pub async fn download(id: u64) -> Result<FirmFile, AppErr> {
    let ff = sqlx::query!("SELECT name FROM tb_firm WHERE id = ?", id)
        .fetch_optional(get_pool())
        .await?
        .ok_or(new_err_msg(ec::FIRM_NOT_EXISTS, ec::FIRM_NOT_EXISTS_MSG))?;
    let data = read_file(id).await?;
    Ok(FirmFile { name: ff.name, body: data})
}
